package com.xbongbong.paas.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.vdurmont.emoji.EmojiParser;
import com.xbongbong.paas.gateway.config.GatewayConstant;
import com.xbongbong.paas.gateway.util.DataBufferUtil;
import com.xbongbong.paas.toolbox.util.StringUtil;
import com.xbongbong.paas.toolbox.wrap.UserVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.LinkedList;
import java.util.Locale;

import static com.xbongbong.paas.gateway.filter.AuthorizationFilter.XBB_GATE_CACHE_USER_KEY;
import static com.xbongbong.paas.gateway.filter.AuthorizationFilter.XBB_GATE_CACHE_USER_NAME_KEY;

/**
 * 修改请求体内容，目前主要处理两件事情
 * 1.过滤html元素等非法字符
 * 2.增加一些用于日志记录的参数
 * 注意：修改请求体内容后需要同时修改请求头重的CONTENT_LENGTH
 * @author kaka
 * @date 2019/1/21 11:14
 * @since v1.0
 * @version v1.0
 */
@Component
public class ModifyRequestBodyFilter implements GlobalFilter, Ordered  {

    private static  final Logger LOG = LoggerFactory.getLogger(ModifyRequestBodyFilter.class);

    /**
     * httpHeader key
     */
    private static final String HTTP_HEADER_KEY = "httpHeader";

    /**
     * 浏览器信息key
     */
    private static final String BROWSER_INFO_KEY = "browserInfo";

    /**
     * ip key
     */
    private static final String IP_KEY = "ip";

    /**
     * 登录员工姓名key, 主要用于记录日志
     */
    private static final String LOGIN_USER_NAME_KEY = "loginUserName";

    /**
     * 登录员工信息key, 用于后续接口的员工信息获取，权限判断等
     */
    private static final String LOGIN_USER_KEY = "loginUser";

    /**
     * 对应BaseDTO中的locale参数
     */
    private static final String LOCALE_KEY = "locale";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        //请求Uri
        URI requestUri = request.getURI();
        String path = requestUri.getPath();
        // 判断请求的链接是否不需要校验
        for (String noFilterPrefix : GatewayConstant.NO_FILTER_PATH_PREFIX) {
            if(path.startsWith(noFilterPrefix)) {
                return chain.filter(exchange);
            }
        }
        // userName 主要用于日志记录
        String userName = (String)exchange.getAttributes().get(XBB_GATE_CACHE_USER_NAME_KEY);
        // user 信息
        UserVO userVO = (UserVO)exchange.getAttributes().get(XBB_GATE_CACHE_USER_KEY);

        // 拷贝一份请求头
        HttpHeaders myHeaders = new HttpHeaders();
        copyMultiValueMap(request.getHeaders(), myHeaders);

        // 获取修改后的请求体
        String bodyString = getModifiedRequestBody(request, myHeaders, userName, userVO);
        DataBuffer bodyDataBuffer = DataBufferUtil.stringBuffer(bodyString);
        int len = bodyDataBuffer.readableByteCount();
        URI ex = UriComponentsBuilder.fromUri(requestUri).build(true).toUri();
        ServerHttpRequest newRequest = request.mutate().uri(ex).build();

        // 修改请求头中CONTENT_LENGTH参数，因为修改了请求体
        myHeaders.remove(HttpHeaders.CONTENT_LENGTH);
        myHeaders.set(HttpHeaders.CONTENT_LENGTH, String.valueOf(len));


        Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);
        newRequest = new ServerHttpRequestDecorator(newRequest) {
            @Override
            public Flux<DataBuffer> getBody() {
                return bodyFlux;
            }

            @Override
            public HttpHeaders getHeaders() {
                return myHeaders;
            }
        };
        HttpHeaders headers = exchange.getResponse().getHeaders();
        // 指定错误返回的Content-Type
        headers.add("Content-Type", MediaType.APPLICATION_JSON_VALUE);
        ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
        return chain.filter(newExchange);
    }

    /**
     *  比AuthorizationFilter优先级低1000
     */
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE + 2002;
    }

    //==================== private method ====================

    /**
     * 将source中的键值对拷贝到MultiValueMap中
     * @param source 来源MultiValueMap
     * @param target 目标MultiValueMap
     */
    private static <K, V> void copyMultiValueMap(MultiValueMap<K,V> source, MultiValueMap<K,V> target) {
        source.forEach((key, value) -> target.put(key, new LinkedList<>(value)));
    }

    /**
     * 获取修改后的请求体内容
     * 1.去掉了非法字符
     * 2.增加了方便日志记录的参数
     * @param request 原请求ServerHttpRequest
     * @param headers 原请求请求头
     * @param userName 操作人姓名，主要用于记录日志
     * @param userVO 登录用户userVo信息
     * @return 修改后的请求体内容
     */
    private String getModifiedRequestBody(ServerHttpRequest request, HttpHeaders headers, String userName, UserVO userVO) {
        Flux<DataBuffer> body = request.getBody();
        StringBuilder sb = new StringBuilder();

        body.subscribe(buffer -> {
            byte[] bytes = new byte[buffer.readableByteCount()];
            buffer.read(bytes);
            DataBufferUtils.release(buffer);
            String bodyString = new String(bytes, StandardCharsets.UTF_8);
            sb.append(bodyString);
        });
        String str = sb.toString();
        // 去除emoji表情
        str = EmojiParser.removeAllEmojis(str);
        // 过滤html元素等非法字符
        // 前端都做了，这个逻辑不需要了 @date 2022/3/28 15:34
//        str = XssShieldUtil.stripXss(str);
        try {
            JSONObject obj = JSON.parseObject(str);
            // 增加方便日志记录的参数
            addLogParams(request, headers, userName, userVO, obj);
            headers.setContentType(MediaType.APPLICATION_JSON);
            //增加语言包参数
            addLocaleParam(request, obj);
            str = JSONObject.toJSONString(obj, SerializerFeature.WriteMapNullValue);
        }catch(Exception e) {
            LOG.error(e.getMessage(), e);
        }
        return str;
    }

    /**
     * 增加语言包参数
     * @param request 原请求ServerHttpRequest
     * @param bodyJson 格式化为 JSONObject 的请求体
     */
    private void addLocaleParam(ServerHttpRequest request, JSONObject bodyJson) {
        String lang = request.getQueryParams().getFirst("lang");
        if (StringUtil.isEmpty(lang)) {
            lang = Locale.CHINA.toString();
        }
        bodyJson.put(LOCALE_KEY, lang);
    }

    /**
     * 增加方便日志记录的参数
     * @param request 原请求ServerHttpRequest
     * @param headers 原请求请求头
     * @param userName 操作人姓名，主要用于记录日志
     * @param userVO 登录用户userVo信息
     * @param bodyJson 格式化为 JSONObject 的请求体
     */
    private void addLogParams(ServerHttpRequest request, HttpHeaders headers, String userName, UserVO userVO, JSONObject bodyJson) {
        String referer = request == null ? "" : headers.getFirst("Referer");
        String path = request == null ? "" : request.getURI().getPath();
        String userAgent = request == null ? "" : headers.getFirst("user-agent");
        String ip = "";
        try {
            assert request != null;
            InetSocketAddress remoteAddress = request.getRemoteAddress();
            assert remoteAddress != null;
            InetAddress inetAddress = remoteAddress.getAddress();
            ip  = inetAddress.getHostAddress();
        } catch (Exception e) {
            LOG.error("ModifyRequestBodyFilter.addLogParams() 报错了 : ", e);
        }
        String httpHeaderTemp = "referer:%s,path:%s,user-agent:%s,ip:%s";
        String httpHeader = String.format(httpHeaderTemp, referer, path, userAgent, ip);
        bodyJson.put(HTTP_HEADER_KEY, httpHeader);
        bodyJson.put(BROWSER_INFO_KEY, userAgent);
        bodyJson.put(IP_KEY, ip);
        bodyJson.put(LOGIN_USER_NAME_KEY, userName);
        bodyJson.put(LOGIN_USER_KEY, userVO);
    }
}
